'''
Created on Sep 28, 2009

@author: Roni
'''

import cProfile
from timerManager import TimerManager
import os
import time
import cliques.utils
import logging
from cliques.algorithmRunner import algorithmRunner

class dynamicRunner(object):
    '''
    Class for running clique search experiments
    '''

    def __init__(self, runner_name,scenario_generator, folder_name=None, algorithms=[],\
                 nodes_range=[20,100,200,400,800],edge_factors=[8,12,16],\
                 k_range=xrange(5,9),num_of_iterations=50,\
                 enable_profiling=False,
                 path_prefix=".."):
        '''
        Constructor
        '''
        # Initialize parameters
        self.folder_name = folder_name
        self.runner_name = runner_name
        self.algorithms = algorithms
        self.scenario_generator = scenario_generator

        self.nodes_range = nodes_range
        self.edge_factors = edge_factors
        self.k_range = k_range
        self.num_of_iterations = num_of_iterations
        self.enable_profiling = enable_profiling
        
        self.results_dir = os.path.join(path_prefix,"resources/results") 
        self.graphs_dir = os.path.join(path_prefix,"resources/graphs") 

    def __load_graph(self,nodes,edges,iteration):
        instance_name = "%s-%d-%d-%d" % (self.folder_name, nodes, edges, iteration)
        file_name = "%s/%s" % (self.graphs_dir,instance_name)
        if os.path.exists(file_name):
            loaded_graph = cliques.utils.import_graph(file_name)
            return cliques.utils.convert_node_names_to_numbers(loaded_graph)
        else:
            return None
        
    def __store_graph(self,a_graph, nodes,edges,iteration):
        instance_name = "%s-%d-%d-%d" % (self.folder_name ,nodes, edges, iteration)
        file_name = "%s/%s" % (self.graphs_dir,instance_name)
        cliques.utils.export_graph(a_graph, file_name)
        
        
        
    def run(self):
        '''
        Run a set of experiments, varying: #nodes, #edges, set of algorithms.
        '''
        continue_old_run=True # If true, will read the output file and continue from the last configuration that was run       
        runner = algorithmRunner()
        timer_manager = TimerManager()
        # Create results files 
        output_file_name = "%s/result-%s.csv" % (self.results_dir,self.runner_name)    
        
        if continue_old_run==False or os.path.exists(output_file_name)==False:
            cliques.utils.backup_old_file(output_file_name)    
            out_file = open(output_file_name, "w")
            self.write_header(out_file,("k", "nodes", "edges", "graph_num_iteration","alg.", "start_node", "iteration","cost", "runtime"))
            old_configurations=[]
        else:
            old_configurations = self.__load_old_results_file(output_file_name)
            out_file = open(output_file_name, "a")
            
    
        for nodes in self.nodes_range:
            for edge_factor in self.edge_factors:
                for k in self.k_range:
                    for graph_iteration in xrange(self.num_of_iterations):
                        exp_scenario = self.scenario_generator.load(nodes,edge_factor,k,graph_iteration)
                        if exp_scenario is None:
                            continue  
                        exp_graph = exp_scenario.unknown_graph                           
                        start_node = exp_scenario.start_node
                        for algorithm in self.algorithms:
                            current_config = (k,nodes,edge_factor,graph_iteration, algorithm.__str__(),int(start_node))
                            if current_config in old_configurations:
                                logging.info("Skipped old configuration %s" % current_config.__str__())
                                continue
                            old_configurations.append(current_config)
                            algorithm.setup(exp_graph,k,start_node)

                            if self.enable_profiling:
                                profiler = cProfile.Profile()
                                profiler.enable(subcalls=True, builtins=True)

                            timer_manager.create_timer("dynamicRunner.runInstance")
                            timer_manager.start("dynamicRunner.runInstance")                                    
                            runner.run(algorithm)
                            timer_manager.stop("dynamicRunner.runInstance")

                           
                            if self.enable_profiling:
                                profiler.disable()
                                profiler.print_stats("time")
                       
                            # Export results
                            algorithm_iteration = algorithm.iteration                                    
                            cost = algorithm.get_cost()
                            algorithm_done = algorithm.done
                            algorithm_statistics = algorithm.statistics()
                            logging.info("%s - [%d] done, success=%s, alg.=%s, cost=%d" % (time.ctime(),graph_iteration,algorithm_done,algorithm,algorithm_iteration))
                            for param in current_config:
                                out_file.write("%s," % param)   
                            out_file.write("%s," % algorithm_iteration)
                            out_file.write("%s," % cost)                                    
                            out_file.write("%s," % timer_manager.get_total_time("dynamicRunner.runInstance"))
                            for stat in algorithm_statistics:
                                out_file.write("%s," % stat)
                            out_file.write("\n")
                            out_file.flush()
           
            
        # Close output files
        out_file.close()


    
    def write_header(self,out_file,file_header_fields):
        for column in file_header_fields:
            out_file.write("%s," % column)    
        out_file.write("\n")

   
    def __load_old_results_file(self,output_file_name):
        ''' Loads the old results file and return a list of previously run configurations '''
        delimiter = ","
        num_of_config_params = 6
        
        out_file = open(output_file_name,"r")
        configurations = []
        for line in out_file:
            parts = line.split(delimiter)
            config = []
            for raw_part in parts:
                part = raw_part.strip()
                if part.isdigit():
                    config.append(int(part))
                else:
                    config.append(part)
            configuration = tuple(config)[:num_of_config_params]
            configurations.append(configuration)

        return configurations